/*
C++实现一个类，该类主要负责日志管理，且类方法在类外实现，该类具备如下功能：
日志输出方法有log_debug、log_info、log_warn、log_error、log_fatal，
且这些类方法的参数支持可变参数的格式化字符串，从而能像C语言的printf一样传入参数，
并且有日志等级控制，分别控制控制台的打印等级，以及写入到日志文件的等级，从info到fatal等级，都有详细的系统日期时间；
并且写入日志文件是线程安全的，日志文件的写入访问支持线程安全和进程安全，
并且日志文件有一个最大容量信息，比如最大20MB，超过这个容量信息，新追加的日志信息会挤掉最老的日志信息。
*/

#include "log_manager.h"

#include <iostream>
#include <fstream>
#include <thread>
#include <chrono>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/file.h>
#include <cstdarg>
#include <ctime>
#include <iomanip>
#include <sstream>

LogManager::LogManager(const std::string &logFilePath, size_t maxSize)
    : logFilePath(logFilePath), maxSize(maxSize), consoleLogLevel(LogLevel::DEBUG), fileLogLevel(LogLevel::DEBUG) {}

LogManager::~LogManager() {}

void LogManager::setLogFile(const std::string &logFile)
{
    logFilePath = logFile;
}

void LogManager::setMaxSize(size_t maxBytes)
{
    maxSize = maxBytes;
}
void LogManager::setConsoleLogLevel(LogLevel level)
{
    consoleLogLevel = level;
}

void LogManager::setFileLogLevel(LogLevel level)
{
    fileLogLevel = level;
}

void LogManager::log_debug(const char *format, ...)
{
    va_list args;
    va_start(args, format);
    log(LogLevel::DEBUG, format, args);
    va_end(args);
}

void LogManager::log_info(const char *format, ...)
{
    va_list args;
    va_start(args, format);
    log(LogLevel::INFO, format, args);
    va_end(args);
}

void LogManager::log_warn(const char *format, ...)
{
    va_list args;
    va_start(args, format);
    log(LogLevel::WARN, format, args);
    va_end(args);
}

void LogManager::log_error(const char *format, ...)
{
    va_list args;
    va_start(args, format);
    log(LogLevel::ERROR, format, args);
    va_end(args);
}

void LogManager::log_fatal(const char *format, ...)
{
    va_list args;
    va_start(args, format);
    log(LogLevel::FATAL, format, args);
    va_end(args);
}

void LogManager::log(LogLevel level, const char *format, va_list args)
{
    std::string message = formatMessage(format, args);
    std::string dateTime = getCurrentDateTime();
    std::string fullMessage = dateTime + " " + getLevelString(level) + ": " + message;
    if (level >= consoleLogLevel)
    {
        std::cout << fullMessage << std::endl;
    }
    if (level >= fileLogLevel)
    {
        std::lock_guard<std::mutex> lock(logMutex);
        int fd = open(logFilePath.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666);
        if (fd != -1)
        {
            flock(fd, LOCK_EX);
            ensureFileSize();
            fullMessage += "\n";
            write(fd, fullMessage.c_str(), fullMessage.length());
            flock(fd, LOCK_UN);
            close(fd);
        }
    }
}

std::string LogManager::getLevelString(LogLevel level)
{
    switch (level)
    {
    case LogLevel::DEBUG:
        return "DEBUG";
    case LogLevel::INFO:
        return "INFO";
    case LogLevel::WARN:
        return "WARN";
    case LogLevel::ERROR:
        return "ERROR";
    case LogLevel::FATAL:
        return "FATAL";
    default:
        return "UNKNOWN";
    }
}

void LogManager::ensureFileSize()
{
    std::ifstream file(logFilePath, std::ios::binary | std::ios::ate);
    if (file.is_open())
    {
        size_t fileSize = file.tellg();
        file.close();
        if (fileSize > maxSize)
        {
            std::ifstream inFile(logFilePath, std::ios::binary);
            std::ostringstream buffer;
            buffer << inFile.rdbuf();
            std::string logContent = buffer.str();
            size_t excessSize = fileSize - maxSize;
            size_t startPos = logContent.find('\n', excessSize);
            if (startPos != std::string::npos)
            {
                logContent = logContent.substr(startPos + 1);
                std::ofstream outFile(logFilePath, std::ios::binary);
                outFile << logContent;
                outFile.close();
            }
        }
    }
}

std::string LogManager::formatMessage(const char *format, va_list args)
{
    va_list argsCopy;
    va_copy(argsCopy, args);
    int len = std::vsnprintf(nullptr, 0, format, argsCopy);
    va_end(argsCopy);

    std::string result(len + 1, '\0');
    std::vsnprintf(&result[0], len + 1, format, args);
    result.resize(len);
    return result;
}

// 将 time_t 时间戳转换为年月日时分秒
static void timestampToDateTime(time_t timestamp, int &year, int &month, int &day, int &hour, int &minute, int &second)
{
    std::tm *timeInfo = std::localtime(&timestamp);
    year = timeInfo->tm_year + 1900;
    month = timeInfo->tm_mon + 1;
    day = timeInfo->tm_mday;
    hour = timeInfo->tm_hour;
    minute = timeInfo->tm_min;
    second = timeInfo->tm_sec;
}

std::string LogManager::getCurrentDateTime()
{
    std::time_t now = std::time(nullptr);
    std::tm* localTime = std::localtime(&now);

    std::ostringstream oss;
    oss << std::put_time(localTime, "%Y-%m-%d %H:%M:%S");
    return oss.str();
}

#if 0  // original example code
int main() {
    LogManager logger("app.log", 10 * 1024 * 1024);
    logger.setConsoleLogLevel(LogLevel::INFO);
    logger.setFileLogLevel(LogLevel::DEBUG);

    logger.log_debug("This is a debug message with number %d and string %s", 123, "test");
    logger.log_info("This is an info message with float %f", 3.14);
    logger.log_warn("This is a warning message");
    logger.log_error("This is an error message");
    logger.log_fatal("This is a fatal message");

    return 0;
}
#endif // if 0